﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using FarseerGames.FarseerPhysics.Collisions;

namespace Kosmos
{
    public class Level : Microsoft.Xna.Framework.DrawableGameComponent
    {
        #region Fields

        //pour la lecture de fichier
        private FileStream fs;
        //pour la lecture de document xml
        private XmlDocument xmldoc;

        // difficulté du level
        private float _difficulty;
        // dimension du level 
        private Vector2 _size;
        public Vector2 Size
        {
            get { return _size; }
        }

        //nombre de ball a initialiser
        private int _nb_balls_init;

        //liste des particules
        private List<Particule> _particules;
        public List<Particule> ListParticule
        {
            get { return _particules; }
        }

        //liste des balls
        private List<Ball> _balls;
        public List<Ball> ListBall
        {
            get { return _balls; }
        }

        //balle du joueur
        private Ball _player_ball;
        public Ball PlayerBall
        {
            get { return _player_ball; }
        }

        //Taille que le joueur doit atteindre pour gagner
        float win_diameter;

        //Surface de dessins
        private SpriteBatch _sprite;

        // texture, chemin et sprite du fond
        private Texture2D _txt2d_background;
        //private String _path_background = "Background";

        // bordures
        private List<Side> _sides;

        private int SIDE_HEIGHT = 30;
        private int SIDE_WIDTH = 30;

        //Tps entre chaque ejection d'une balle lorsqu'on clique en continue sur la souris
        private float _time;
        private int _ejected;
        //Pour verouiller le clique drois de la souris lorsqu'on reste appuyer dessus
        private bool _lock_right_click;

        #endregion

        #region Initialization

        public Level(MyGame game, int level)//, Vector2 size, float level)
            : base(game)
        {
            _difficulty = level;

        }

        //Chargement du fichier XML et de quelque donnée du level
        private void LoadFromXML()
        {

            string num = _difficulty.ToString();

            if (_difficulty < 10)
                num = num.Insert(0, "0");

            fs = new FileStream(Game.Content.RootDirectory + "\\Levels\\level" + num + ".xml",
                    FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            xmldoc = new XmlDocument();
            xmldoc.Load(fs);

            _txt2d_background = TexturesPool.GetTexture(xmldoc.SelectSingleNode("//level/background").InnerText);

            XmlNodeList xmlnd = xmldoc.GetElementsByTagName("size");
            _size = new Vector2(
                int.Parse(xmldoc.SelectSingleNode("//level/size/width").InnerText),
                int.Parse(xmldoc.SelectSingleNode("//level/size/height").InnerText));

            _nb_balls_init = int.Parse(xmldoc.SelectSingleNode("//level/standard_ball/number").InnerText);
            win_diameter = float.Parse(xmldoc.SelectSingleNode("//level/player_ball/size_win").InnerText);
        }

        public override void Initialize()
        {
            _balls = new List<Ball>();
            _particules = new List<Particule>();
            _sides = new List<Side>();

            LoadFromXML();
            InitLevel();//Initialise le niveau a partir du fichier xml

            _lock_right_click = false;

            _sprite = ((MyGame)Game).Sprite;

            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle(0, -SIDE_HEIGHT, (int)Size.X, SIDE_HEIGHT),
                "Graphics\\BorderTop"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle(0, (int)Size.Y, (int)Size.X, SIDE_HEIGHT),
                "Graphics\\BorderBottom"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle(-SIDE_WIDTH, 0, SIDE_WIDTH, (int)_size.Y),
                "Graphics\\BorderLeft"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle((int)_size.X, 0, SIDE_WIDTH, (int)_size.Y),
                "Graphics\\BorderRight"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle(-SIDE_WIDTH, -SIDE_HEIGHT, SIDE_WIDTH, SIDE_HEIGHT),
                "Graphics\\CornerTopLeft"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle((int)_size.X, -SIDE_HEIGHT, SIDE_WIDTH, SIDE_HEIGHT),
                "Graphics\\CornerTopRight"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle(-SIDE_WIDTH, (int)_size.Y, SIDE_WIDTH, SIDE_HEIGHT),
                "Graphics\\CornerBottomLeft"));
            _sides.Add(new Side(
                (MyGame)Game,
                new Rectangle((int)Size.X, (int)Size.Y, SIDE_WIDTH, SIDE_HEIGHT),
                "Graphics\\CornerBottomRight"));

            // initialisation
            foreach (Particule p in _particules)
                p.Initialize();
            foreach (Ball b in _balls)
                b.Initialize();
            foreach (Side s in _sides)
                s.Initialize();

            base.Initialize();
        }

        protected void InitLevel()
        {
            float _player_ball_diameter = 2f * float.Parse(xmldoc.SelectSingleNode("//level/player_ball/radius").InnerText);
            int player_ball_x = int.Parse(xmldoc.SelectSingleNode("//level/player_ball/x").InnerText);
            int player_ball_y = int.Parse(xmldoc.SelectSingleNode("//level/player_ball/y").InnerText);

            _player_ball = new Ball(Game, new Vector2(player_ball_x, player_ball_y), _player_ball_diameter);
            Ball.player_ball = _player_ball;

            _balls.Insert(0, _player_ball);

            _lock_right_click = false;

            XmlNodeList sun_xmln = xmldoc.SelectNodes("//level/sun_ball");
            int nb_sun_ball = sun_xmln.Count;


            List<BallSun> listSun = new List<BallSun>();
            if (nb_sun_ball > 0)
            {
                foreach (XmlNode node in sun_xmln)
                {
                    float sun_ball_diameter = 2f * float.Parse(node.SelectSingleNode("radius").InnerText);
                    int sun_ball_x = int.Parse(node.SelectSingleNode("x").InnerText);
                    int sun_ball_y = int.Parse(node.SelectSingleNode("y").InnerText);
                    BallSun sun = new BallSun(Game, new Vector2(sun_ball_x, sun_ball_y), sun_ball_diameter);
                    _balls.Insert(0, sun);
                    listSun.Add(sun);
                }
            }

            XmlNodeList xmln = xmldoc.SelectNodes("//level/green_ball");
            int nb_green_ball = xmln.Count;
            if (nb_green_ball > 0)
            {
                foreach (XmlNode node in xmln)
                {
                    float green_ball_diameter = 2f * float.Parse(node.SelectSingleNode("radius").InnerText);
                    int green_ball_x = int.Parse(node.SelectSingleNode("x").InnerText);
                    int green_ball_y = int.Parse(node.SelectSingleNode("y").InnerText);
                    BallGreen green = new BallGreen(Game, new Vector2(green_ball_x, green_ball_y), green_ball_diameter);
                    _balls.Insert(0, green);
                }
            }
            //_balls.Insert(0,new BallGreen(Game, new Vector2(500, 260), 50));

            float min_diameter = float.Parse(xmldoc.SelectSingleNode("//level/standard_ball/size_min").InnerText);
            float max_diameter = float.Parse(xmldoc.SelectSingleNode("//level/standard_ball/size_max").InnerText);

            for (int i = 0; i < _nb_balls_init; ++i)
            {
                Vector2 pos;
                float diameter;
                bool IsInCollision;
                do
                {
                    IsInCollision = true;

                    float x = (float)(MyGame.random.NextDouble() * Size.X);
                    float y = (float)(MyGame.random.NextDouble() * Size.Y);
                    pos = new Vector2(x, y);
                    diameter = min_diameter + (float)(Math.Pow(MyGame.random.NextDouble(), 2) * max_diameter);
                    //diameter = 20;
                    if (x < (diameter / 2f) || y < (diameter / 2f))
                        continue;

                    bool IsInCollisionWithBall = false;
                    // verification de collision avec la balle du joueur
                    if (x + (diameter / 2f) >= _player_ball.left &&
                        x - (diameter / 2f) <= _player_ball.right &&
                        y + (diameter / 2f) >= _player_ball.top &&
                        y - (diameter / 2f) <= _player_ball.bottom)
                    {
                        continue;
                    }

                    foreach (Ball ball in _balls)
                    {
                        // verification de collision avec les balles deja créé
                        if (x + (diameter / 2f) >= ball.left
                            && x - (diameter / 2f) <= ball.right
                            && y + (diameter / 2f) >= ball.top
                            && y - (diameter / 2f) <= ball.bottom)
                        {
                            IsInCollisionWithBall = true;
                            break;
                        }
                    }

                    // verification que la balle ne deborde pas du jeu
                    IsInCollision = (x + (diameter / 2f) >= Size.X) ||
                                    (y + (diameter / 2f) >= Size.Y) ||
                                    IsInCollisionWithBall;
                }
                while (IsInCollision);

                _balls.Insert(0, new Ball(Game, pos, diameter));
                //Game.Components.Add(_balls[i]);
            }

            // On met en orbite les balles avec le soleil, si il y en a un

            foreach (BallSun sun in listSun)
                foreach (Ball b in _balls)
                {
                    if (sun == b) continue;

                    sun.MettreEnOrbite(b);
                }

        }

        #endregion

        #region Update and Draw

        public override void Update(GameTime gameTime)
        {
            //si le joueur gagne
            if (_player_ball.diameter >= win_diameter)
                EndOfGame(true);

            GestionActionSouris(gameTime);

            //Gestion des particules
            for (int i = 0; i < _particules.Count; )
            {
                _particules[i].Update(gameTime);
                if (_particules[i].isDead)
                {
                    _particules[i].Geom.Body.Dispose();
                    _particules[i].Geom.Dispose();

                    ((MyGame)Game).Simulator.Remove(_particules[i].Geom.Body);
                    ((MyGame)Game).Simulator.Remove(_particules[i].Geom);
                    _particules.Remove(_particules[i]);
                }
                else
                    ++i;
            }

            //Gestion des bordure
            foreach (Side s in _sides)
                s.Update(gameTime);

            //Gestion des balls
            for (int i = 0; i < _balls.Count; )
            {
                if (_balls[i].diameter < Ball._sz_min_ball)
                {
                    remove(_balls[i]);

                    if (_balls[i] == _player_ball)
                    {
                        EndOfGame(false);
                    }


                    ((MyGame)Game).Level.ListBall.Remove(_balls[i]);
                }
                else
                {
                    _balls[i].Update(gameTime);
                    ++i;
                }
            }
        }

        //Supprime les balle du moteur physique
        public void remove(Ball b)
        {
            b.Geom.Body.Dispose();
            b.Geom.Dispose();

            ((MyGame)Game).Simulator.Remove(b.Geom.Body);
            ((MyGame)Game).Simulator.Remove(b.Geom);
        }


        public override void Draw(GameTime gameTime)
        {
            foreach (Particule p in _particules)
                p.Draw(gameTime);
            foreach (Side s in _sides)
                s.Draw(gameTime);
            foreach (Ball b in _balls)
                b.Draw(gameTime);
        }
        /*
        private void DrawBackground()
        {
            Rectangle rect = new Rectangle(
                0,
                0,
                _txt2d_background.Width,
                _txt2d_background.Height
            );

            _sprite.Draw(
                _txt2d_background,
                rect,
                Color.White
            );
        }
        */

        public void EndOfGame(bool victory)
        {
            string message = (victory ? "!! Victory !!" : "!! You loose !!")
                                            + "\nPress Enter to return to the main menu."
                                            + "\nPress Escape to quit the game.";
            
            this.Dispose();
            ((MyGame)Game).Components.Remove(this);
            ((MyGame)Game).LevelIsRunning = false;
            ((MyGame)Game).ScreenManager.AddScreen(new GameOverScreen(message), null);
            ((MyGame)Game).CleanLevel();
        }


        #endregion

        #region Handle Inputs

        // clique de la souris
        protected void GestionActionSouris(GameTime gameTime)
        {
            if (ButtonState.Pressed == Mouse.GetState().RightButton)
            {
                if (!_lock_right_click)
                {
                    ((MyGame)Game).TimeValue = (((MyGame)Game).TimeValue == 1) ? 0.125f : 1f;
                    _lock_right_click = true;
                }
            }
            if (ButtonState.Released == Mouse.GetState().RightButton)
            {
                _lock_right_click = false;
            }

            _time += gameTime.ElapsedGameTime.Milliseconds;
            if ((!((MyGame)Game).isGameOver) && ButtonState.Pressed == Mouse.GetState().LeftButton)
            {
                int tmp = (int)(_time - gameTime.ElapsedGameTime.Milliseconds * ((MyGame)Game).TimeValue) / 500;
                if (tmp > _ejected)
                {
                    _ejected = tmp;

                    int x = Mouse.GetState().X;
                    int y = Mouse.GetState().Y;
                    float oX = ((MyGame)Game).GraphicsDeviceManager.PreferredBackBufferWidth / 2f;
                    float oY = ((MyGame)Game).GraphicsDeviceManager.PreferredBackBufferHeight / 2f;

                    float angle = (float)Math.Atan2(y - oY, x - oX);
                    angle += (float)Math.PI;
                    Vector2 vec = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
                    vec *= _player_ball.Geom.Body.Mass * 10f;

                    _player_ball.Geom.Body.ApplyImpulse(vec);

                    //angle = (float)Math.Atan2(-vec.Y, -vec.X);

                    angle += (float)Math.PI;

                    float aire = _player_ball.aire * Ball._percent_eject_when_move;

                    _player_ball.aire -= aire;
                    //_player_ball.Geom.Body.Position = new Vector2(_player_ball.Geom.Position.X, _player_ball.Geom.Position.Y);

                    float diameter = (float)Math.Sqrt(aire / Math.PI) * 2f;

                    float dist = (_player_ball.diameter + diameter) / 2;
                    float xb = (float)(Math.Cos(angle) * dist + _player_ball.centerX);
                    float yb = (float)(Math.Sin(angle) * dist + _player_ball.centerY);

                    Ball b = new Ball(Game, new Vector2(xb, yb), diameter);
                    /*
                    Vector2 vec2 = _player_ball.Geom.Body.GetVelocityAtLocalPoint(_player_ball.Geom.Body.Position);//new Vector2(-vec.X, -vec.Y);
                    vec2 *= b.Geom.Body.Mass / _player_ball.Geom.Body.Mass;
                    b.Geom.Body.ApplyImpulse(vec2);
                    */
                    Vector2 vec2 = new Vector2(-vec.X, -vec.Y);
                    vec2 *= 0.25f;
                    b.Geom.Body.ApplyImpulse(vec2);

                    _balls.Insert(0, b);


                    for (int i = 0; i < 50; ++i)
                    {
                        Particule p = new Particule(Game, new Vector2(xb, yb), 2);
                        p.Initialize();

                        angle += (float)(-Math.PI / 10.0 + MyGame.random.NextDouble() * Math.PI / 5.0);

                        float force = 100f + (float)MyGame.random.NextDouble() * 1000f;
                        force /= 2;

                        float xp = (float)(Math.Cos(angle) * force);
                        float yp = (float)(Math.Sin(angle) * force);

                        Vector2 dir = new Vector2(xp, yp);
                        p.Geom.Body.ApplyImpulse(dir);

                        _particules.Add(p);
                    }
                }
            }
            else if (ButtonState.Released == Mouse.GetState().LeftButton)
            {
                _time = 0f;
                _ejected = -1;
            }
        }

        #endregion
    }
}